using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Reflection;

namespace AutoFormatter
{
    #region enums
    /// <summary>
    /// Possible modes of controls which can be used to alternate between <see cref="BlockMode"/>s
    /// </summary>
    public enum ButtonMode
    {
        Radio, Checkbox, Button,TextOnly, Hyperlink, None
    }
    /// <summary>
    /// Different states of the 'codeblock'
    /// </summary>
    [Flags()]
    public enum BlockMode
    {
        Hide = 1, Scroll = 2, Full = 4
    }

    /// <summary>
    /// Possible borderstyles. Source: http://msdn.microsoft.com/library/default.asp?url=/workshop/author/dhtml/reference/properties/borderstyle.asp
    /// </summary>
    public enum BorderStyles
    {
        /// <summary>
        /// no border
        /// </summary>
        none,
        /// <summary>
        /// Border is a dotted line. This value is supported on the Macintosh platform, as of Microsoft Internet Explorer 4.01, and on the Microsoft Windows platform, as of Internet Explorer 5.5. It renders as a solid line on Unix platforms, and on Windows systems running earlier versions of Internet Explorer. 
        /// </summary>
        dotted,
        /// <summary>
        /// Border is a dashed line. This value is supported on the Macintosh platform as of Internet Explorer 4.01 and on the Windows platform, as of Internet Explorer 5.5. It renders as a solid line on Unix platforms, and on Windows systems running earlier versions of Internet Explorer. 
        /// </summary>
        dashed,
        /// <summary>
        /// Border is a solid line. 
        /// </summary>
        solid,
        /// <summary>
        /// Border is a double line drawn on top of the background of the object. The sum of the two single lines and the space between equals the borderWidth value. The border width must be at least 3 pixels wide to draw a double border. 
        /// </summary>
        Double,
        /// <summary>
        /// 3-D groove is drawn in colors based on the value. The borderWidth property of the object must be specified for the style to render correctly. 
        /// </summary>
        groove,
        /// <summary>
        ///  3-D ridge is drawn in colors based on the value. 
        /// </summary>
        ridge,
        /// <summary>
        /// 3-D inset is drawn in colors based on the value. 
        /// </summary>
        inset,
        /// <summary>
        /// Border is the same as inset, but is surrounded by an additional single line, drawn in colors based on the value. 
        /// </summary>
        window_inset,
        /// <summary>
        /// 3-D outset is drawn in colors based on the value.
        /// </summary>
        outset

    }
    #endregion

    static class html
    {
        #region Formatting
        const int maxhtmlcolourvalue = (1 << 24) - 1;

        public static string ColToHex(Color c)
        {
            if (c.IsEmpty || c == Color.Transparent)
                return "Transparant";
            return "#" + ColToInt(c).ToString("x").PadLeft(6, '0'); ;
        }

        public static int ColToInt(Color c)
        {
            return c.ToArgb() & maxhtmlcolourvalue;
        }

        public static string DecToPX(decimal d)
        {
            return d.ToString() + "px";
        }

        public static string FloatToPT(float f)
        {
            return Math.Round(f).ToString() + "pt";
        }


        public static string GetBorderStyleString(BorderStyles BorderStyle)
        {

            if (BorderStyle == BorderStyles.window_inset)
                return "window-inset";
            else if (BorderStyle == BorderStyles.none)
                return "''";
            return BorderStyle.ToString();

        }
        #endregion
    }

    #region HTML objects
    
    /// Started out using mshmtl objects, but didn't like some of the features
    /// such as the splitting up of geneneral border properties in top,bottom,left and right seperately.
    /// To keep the parsing minimized, went back to the original setup of using a stringbuilder
    /// directly

    #region Base
    abstract class CanAppend
    {
        public CanAppend()
        {
            SearchAttributes = GetShouldSearchAttributes();
            Attributes = new AttributeCollection(this);
        }

        public readonly AttributeCollection Attributes;

        public virtual StringBuilder Append(StringBuilder sb)
        {
            AppendAttributes(sb);
            return sb;
        }

        public virtual bool ShouldAppend
        {
            get { return true; }
        }
        #region html attributes
        protected virtual IEnumerable<HTMLAttribute> GetAppendableAttributes()
        {
            if(SearchAttributes)
                foreach (HTMLAttribute ha in GetAppendableAttributes(this))
                    yield return ha;
            foreach (HTMLAttribute ha in Attributes)
                yield return ha;
        }

        /// <summary>
        /// Inheriting classes can set this value to false if they always give all Attributes
        /// and the fields need not be searched.
        /// If inheriting classes keep this value on true (default), <see cref="GetAppendableAttributes"/>
        /// will search fields for <see cref="HTMLAttribute"/> objects, so the inheriting class,
        /// doesn't have to return them manually.
        /// </summary>
        protected bool SearchAttributes;
        /// <summary>
        /// Gives inheriting classes the opertunity to set <see cref="SearchAttributes"/> without
        /// having to construct a contructor
        /// </summary>
        /// <returns></returns>
        protected virtual bool GetShouldSearchAttributes()
        {
            return true;
        }

        class ReflectionAttribute : HTMLAttribute
        {
            FieldInfo fi;
            public ReflectionAttribute(FieldInfo fi)
                : base(fi.Name.Replace('_','-'))
            {
                this.fi = fi;
            }
            private CanAppend curobject;
            public CanAppend CurrentObject
            {
                get { return curobject; }
                set
                {
                    curobject = value;
                    Value = fi.GetValue(value) as CanAppend;
                }
            }
        }
        static Dictionary<Type, List<ReflectionAttribute>> defaultAttributes = new Dictionary<Type, List<ReflectionAttribute>>();
        protected static IEnumerable<HTMLAttribute> GetAppendableAttributes(CanAppend c)
        {
            Type t = c.GetType();
            List<ReflectionAttribute> list;
            if (!defaultAttributes.TryGetValue(t, out list))
            {
                list = new List<ReflectionAttribute>();
                foreach (FieldInfo fi in t.GetFields())
                    if (typeof(CanAppend).IsAssignableFrom(fi.FieldType))
                    {
                        list.Add(new ReflectionAttribute(fi));
                    }
                defaultAttributes[t] = list;
            }

            foreach (ReflectionAttribute ra in list)
            {
                ra.CurrentObject = c;
                yield return ra;
            }
        }

        int depth;

        protected void AppendAttributes(StringBuilder sb)
        {
            foreach (HTMLAttribute ha in GetAppendableAttributes())
            {
                if (ha.Value == null || ! ha.Value.ShouldAppend) continue;

                bool NeedQuotes = ha.Value.NeedQuotes;
                ha.Value.depth = NeedQuotes ? depth + 1 : depth;
                
                char quote = depth == 0 ? '"' : '\'';
                sb.Append(' ').Append(ha.Name).Append("=");
                if(NeedQuotes)sb.Append(quote);
                
                ha.Value.Append(sb);                
                //closing quote
                if(NeedQuotes)sb.Append(quote);
                if(depth>0)sb.Append(';');
            }
            AppendCustomAttributes(sb);
        }

        /// <summary>
        /// Gives custom classes the possibility to append custom attributes before
        /// the closing of the quote (if any).
        /// </summary>
        /// <param name="sb"></param>
        protected virtual void AppendCustomAttributes(StringBuilder sb)
        {
        }

        /// <summary>
        /// Only applies if the inheriting is an Attribute
        /// </summary>
        protected virtual bool NeedQuotes { get { return false; } }

        public void AddAttribute<T>(string Name, T value)
        {
            CanAppend c = value is CanAppend ? value as CanAppend : (Element<T>)value;
            Attributes.Add(new HTMLAttribute(Name,c));            
        }
        #endregion
    }

    class AttributeCollection:IEnumerable<HTMLAttribute>
    {
        public readonly CanAppend Parent;
        public AttributeCollection(CanAppend Parent)
        {
            this.Parent = Parent;
        }
        readonly List<HTMLAttribute> list = new List<HTMLAttribute>();
        public void Add(HTMLAttribute ha)
        {
            ha.col = this;
            list.Add(ha);
        }

        public int Count { get { return list.Count; } }

        #region IEnumerable<HTMLAttribute> Members

        public IEnumerator<HTMLAttribute> GetEnumerator()
        {
            return list.GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            throw new Exception("The method or operation is not implemented.");
        }

        #endregion
    }

    class HTMLAttribute
    {
        public readonly string Name;
        public CanAppend Value;
        internal AttributeCollection col;
        public HTMLAttribute(string Name)
        {
            this.Name = Name;
        }
        public HTMLAttribute(string Name,CanAppend Value):this(Name)
        {
            this.Value = Value;
        }
    }
    #endregion

    #region Childs
    class Style:CanAppend
    {
        public PointValue Font_Size;
        public Element<FontWeight> Font_Weight;
        public TextElement Font_Family;

        public Border Border,Border_Bottom,Border_Left,Border_Right;
        public PointValue Margin_Left, Margin_Right,Margin_Top,Margin_Bottom;
        public TextElement Display;
        public TextElement Cursor;
        public ColorElement BackGround_Color;
        public TextElement overflow;
        public PointValue Height,Width;
        /// <summary>
        /// Hard coded style settings, this can include any property already in the available
        /// class properties, but also any not included.
        /// </summary>
        public string CustomStyleSettings;

        protected override bool NeedQuotes { get { return true; } }

        protected override void AppendCustomAttributes(StringBuilder sb)
        {
            base.AppendCustomAttributes(sb);
            if (CustomStyleSettings != null && CustomStyleSettings.Trim().Length > 0)
            {
                sb.Append(CustomStyleSettings);
            } 

        }
    }

    class Element<T>:CanAppend
    {
        public Element()
        {
        }
        public Element(T Value)
        {
            this.Value = Value;
        }
        public T Value;

        public static implicit operator Element<T>(T value)
        {
            Element<T> o = new Element<T>();
            o.Value = value;
            return o;
        }

        public override StringBuilder Append(StringBuilder sb)
        {
            return sb.Append(Value);
        }
    }

    class TextElement : Element<string>
    {
        public TextElement() { }
        public TextElement(string Value) : base(Value) { }
        public static implicit operator TextElement(string value)
        {
            TextElement o = new TextElement();
            o.Value = value;
            return o;
        }
        protected override bool NeedQuotes { get { return true; } }
    }

    class PointValue : Element<int>
    {
        public override StringBuilder Append(StringBuilder sb)
        {
            return base.Append(sb).Append("pt");
        }
        public static implicit operator PointValue(int value)
        {
            PointValue o = new PointValue();
            o.Value = value;
            return o;
        }
    }

    class ColorElement:Element<Color>
    {
        public override StringBuilder Append(StringBuilder sb)
        {
            return sb.Append(html.ColToHex(Value));
        }

        public static implicit operator ColorElement(Color c)
        {
            ColorElement o = new ColorElement();
            o.Value = c;
            return o;
        }
    }

    enum FontWeight
    {
        normal,bold,bolder
    }


    class Border:CanAppend
    {
        public BorderStyles Style = BorderStyles.solid;
        public int Width = 1;
        public Color Color = Color.Black;

        public override StringBuilder Append(StringBuilder sb)
        {
            return sb
                .Append(html.DecToPX(Width))
                .Append(' ')
                .Append(html.ColToHex(Color))
                .Append(' ')
                .Append(html.GetBorderStyleString(Style));
        }

        protected override bool NeedQuotes{get{return true;}}
    }

    abstract class HtmlNode:CanAppend
    {
        public HtmlNode()
        {
            Tag = getTag();
        }
        public readonly Style Style = new Style();
        public TextElement OnClick,OnDblClick,OnKeyDown;
        public readonly List<CanAppend> Children = new List<CanAppend>();

        public readonly string Tag;

        protected abstract string getTag();



        public override bool ShouldAppend
        {
            get { return base.ShouldAppend || Children.Count>0; }
        }

        public void AddText(string text)
        {
            Children.Add((TextElement)text);
        }

        public override StringBuilder Append(StringBuilder sb)
        {
            Open(sb);
            foreach (CanAppend child in Children)
                child.Append(sb);
            Close(sb);
            return sb;
        }

        /// <summary>
        /// Open the html tag (including style and other settings)
        /// </summary>
        /// <param name="sb"></param>
        protected void Open(StringBuilder sb)
        {
            sb.Append('<')
                .Append(Tag);

            AppendAttributes(sb);

            sb.Append('>');
        }

        /// <summary>
        /// Close the html tag
        /// </summary>
        /// <param name="sb"></param>
        protected void Close(StringBuilder sb)
        {
            sb.Append("</").Append(Tag).Append('>');
        }
    }

    class Div : HtmlNode
    {
        protected override string getTag()
        {
            return "DIV";
        }
    }
    class Span:HtmlNode
    {
        protected override string getTag()
        {
            return "SPAN";
        }
    }

    class InputElement:HtmlNode
    {
        public TextElement Type;
        public TextElement Value;

        protected override string getTag()
        {
            return "Input";
        }

        public override StringBuilder Append(StringBuilder sb)
        {
            Open(sb);
            return sb;
        }
    }

    class Anchor : HtmlNode
    {
        public TextElement href;

        protected override string getTag()
        {
            return "a";
        }
    }

    class Comment : TextElement
    {
        public Comment() { }
        public Comment(string text) : base(text) { }
        public override StringBuilder Append(StringBuilder sb)
        {
            return sb.Append("<!-- ").Append(Value).Append("-->");
        }
    }
    #endregion
    #endregion
}
